package com.youlu.campus.admin.auth.shiro.filter;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.youlu.campus.admin.auth.common.DeviceType;
import com.youlu.campus.admin.auth.entity.SystemLoginLog;
import com.youlu.campus.admin.auth.entity.exception.AuthErrorCode;
import com.youlu.campus.admin.auth.shiro.exception.AuthException;
import com.youlu.campus.admin.auth.shiro.token.AuthToken;
import com.youlu.campus.admin.auth.shiro.token.AuthType;
import com.youlu.campus.admin.utils.CommonUtils;
import com.youlu.campus.base.exception.BusinessException;
import com.youlu.campus.common.exception.ApiErrorCode;
import com.youlu.campus.common.utils.SM2CryptUtils;
import com.youlu.campus.entity.WechatAuthBlackList;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * Created by zhuhuaiqi
 *
 * @author zhuhuaiqi
 */
public class AuthcAuthenticationFilter extends AuthenticationFilter {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    public static final String DEFAULT_USERNAME_PARAM = "username";
    public static final String DEFAULT_PASSWORD_PARAM = "password";
    public static final String DEFAULT_DOMAIN_PARAM = "domain";

    public static final String DEFAULT_REMEMBER_ME_PARAM = "rememberMe";

    public static final String DEFAUL_USER_ID_PARAM = "userId";

    public static final String DEFAUL_COM_ID_PARAM = "comId";

    private String usernameParam = DEFAULT_USERNAME_PARAM;
    private String passwordParam = DEFAULT_PASSWORD_PARAM;
    private String domainParam = DEFAULT_DOMAIN_PARAM;

    //private String rememberMeParam = DEFAULT_REMEMBER_ME_PARAM;

    private String userIdParam = DEFAUL_USER_ID_PARAM;

    private String comIdParam = DEFAUL_COM_ID_PARAM;

    private MongoTemplate mongoTemplate;

    public AuthcAuthenticationFilter(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws
            Exception {
        logger.info(":>>> 开始执行executeLogin请求");
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            logger.error(":>>> 创建的token为空,返回失败");
            return false;
        }
        logger.info(":>>> 创建的token:{}", JSON.toJSONString(token));
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthException ae) {
            logger.error(":>>> AuthcAuthenticationFilter.executeLogin AuthException:{}", ae);
            return onLoginFailure(token, ae, request, response);
        } catch (AuthenticationException e) {
            logger.error(":>>> AuthcAuthenticationFilter.executeLogin AuthenticationException:{}", e);
            return onLoginFailure(token, e, request, response);
        }
    }

    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // get the request
        HttpServletRequest request1 = requestAttributes.getRequest();
        String sai = request1.getHeader("sai");
        WechatAuthBlackList wechatAuthBlackList = this.get(sai);
        if (Objects.isNull(wechatAuthBlackList)) {
            logger.error(":>>> 未配置平台开放信息，请联系管理员");
            throw new BusinessException("未配置平台开放信息，请联系管理员");
        }
        String username = getUsername(request);
        username= SM2CryptUtils.decrypt(username,wechatAuthBlackList.getPrivatekey());
        String password = getPassword(request);
        password= SM2CryptUtils.decrypt(password,wechatAuthBlackList.getPrivatekey());
        logger.info(":>>> sai:{},解密的用户和密码:{}:{}", sai,username,password);
        String domain = getDomain(request);
        //boolean rememberMe = isRememberMe(request);
        String host = getHost(request);
        boolean rememberMe = false;
        logger.info(":>>> 创建token,userName:{},password:{},domain:{},host:{}", username, password, domain, host);
        return new AuthToken(username, password, domain, rememberMe, host, AuthType.USERNAME_PASSWORD,
                DeviceType.WEB);
    }

    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws
            Exception {
        // TODO:需要记录登录IP和时间
        logger.info(":>>> 登陆成功:{}", JSON.toJSON(token));
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        AuthToken authToken = (AuthToken) token;
        logger.info(":>>> 登陆成功,onLoginSuccess with authToken:{}", JSON.toJSONString(authToken));

        logger.debug("onLoginSuccess getParameterMap:{}", JSON.toJSONString(httpRequest
                .getParameterMap()));
        logger.debug("onLoginSuccess X-Requested-With:{}", JSON.toJSONString(httpRequest.getHeaders
                ("X-Requested-With")));

        logger.debug("method:{}, queryString:{}, URI:{}, URL:{}", httpRequest.getMethod(),
                httpRequest.getQueryString(), httpRequest.getRequestURI(), httpRequest.getRequestURL());

        String successUrl = getSuccessUrl();
        boolean contextRelative = true;

        if (httpRequest.getMethod().equalsIgnoreCase(AccessControlFilter.POST_METHOD)) {
            String targetUrl = httpRequest.getParameter("targetUrl");
            if (StringUtils.hasText(targetUrl)) {
                successUrl = targetUrl;
                logger.debug("onLoginSuccess savedRequest is null successUrl:{}", JSON
                        .toJSONString(successUrl));
            }
        }

        String accessToken = authToken.getAccessToken();
        //logger.debug("------->>>>successUrl:{}, accessToken:{}", successUrl, accessToken);
        storeUsername(authToken.getUsername(), httpRequest, httpResponse);

        storeUserid(authToken.getUid(), httpRequest, httpResponse);

        storeDomain(authToken.getDomain(), httpRequest, httpResponse);

        storeComId(authToken.getComId(), httpRequest, httpResponse);

        //storeAccessToken(accessToken, authToken.getExpiresIn(), httpRequest, httpResponse);
        storeAccessToken(accessToken, httpRequest, httpResponse);

        if (isAjaxRequest(request)) {
            Map<String, Object> data = Maps.newHashMapWithExpectedSize(6);
            data.put(getAccessToken(), authToken.getAccessToken());
            logger.info(":>>> token 过期时间:{},更新后时间:{}", authToken.getExpiresIn(), authToken.getExpiresIn() + 86400);
            data.put("expires_in", authToken.getExpiresIn());

            data.put("id", authToken.getUid());
            data.put("loginName", authToken.getUsername());
            data.put("domain", authToken.getDomain());
            data.put("roles", authToken.getRoles());
            com.youlu.campus.admin.utils.WebUtils.outJSON(response,
                    AuthErrorCode.LOGIN_SUCCESS, data);
            request.setAttribute(getAccessToken(), authToken.getAccessToken());
            // 添加登录日志
            SystemLoginLog loginLog = new SystemLoginLog();
            String ip = CommonUtils.getRemoteIp(httpRequest);
            loginLog.setLoginTime(LocalDateTime.now());
            loginLog.setUserName(authToken.getUsername());
            loginLog.setIpAddress(ip);
            loginLog.setCreatedTime(new Date());
            loginLog.setType("login-ok");
            mongoTemplate.insert(loginLog);
            return true;
        } else {
            WebUtils.issueRedirect(request, response, successUrl);
        }
        return true;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object
            mappedValue) throws Exception {

        boolean r = super.isAccessAllowed(request, response, mappedValue);
        logger.info(":>>> 访问允许,开始登陆验证:{}", r);
        return r;
    }

    protected boolean onLoginFailure(AuthenticationToken token, Exception ex,
                                     ServletRequest request, ServletResponse response) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        logger.debug("onLoginFailure loginUrl:{}", getLoginUrl());
        String errorMsg = "服务异常";
        ApiErrorCode errorCode = null;
        if (ex instanceof UnknownAccountException) {
            errorCode = AuthErrorCode.USER_NOT_EXISTS;
        } else if (ex instanceof AuthException) {
//            errorCode = AuthErrorCode.INVALID_ARGUMENT;
            errorCode = ((AuthException) ex).getErrorCode();
//            errorCode = ((AuthException) ex).getErrorCode();
        } else if (ex instanceof AuthenticationException) {
            errorCode = AuthErrorCode.LOGIN_EX;
        }
        logger.warn(":>>> 登陆失败: error code:{}, message:{}",
                errorCode.getCode(), errorCode.getMessage());

        if (isAjaxRequest(request)) {
            com.youlu.campus.admin.utils.WebUtils.outJSON(response, errorCode);
        } else {
            httpRequest.setAttribute(getErrorMessageParam(), errorCode.getMessage());
        }
        return false;
    }


    protected void storeUsername(String username, HttpServletRequest request,
                                 HttpServletResponse response) {
        Cookie cookie = getUsernameCookie();
        logger.info(":>>> userName cookie:{}", JSON.toJSONString(cookie));
        cookie.setValue(username);
        setCookieSecure(cookie);
        cookie.saveTo(request, response);
        logger.info(":>>> Set username cookie for session with {}", username);
    }

    protected void storeUserid(String userId, HttpServletRequest request,
                               HttpServletResponse response) {
        Cookie cookie = getUserIdCookie();
        logger.info(":>>> userId cookie:{}", JSON.toJSONString(cookie));
        cookie.setValue(userId);
        this.setCookieSecure(cookie);
        cookie.saveTo(request, response);
        logger.info(":>>> Set userId cookie for session with {}", userId);
    }

    protected void storeComId(String comId, HttpServletRequest request,
                              HttpServletResponse response) {
        Cookie cookie = getComIdCookie();
        logger.info(":>>> comId cookie:{}", JSON.toJSONString(cookie));
        cookie.setValue(comId);
        this.setCookieSecure(cookie);
        cookie.saveTo(request, response);
        logger.info(":>>> Set comId cookie for session with {}", comId);
    }

    protected void storeDomain(String domain, HttpServletRequest request,
                               HttpServletResponse response) {
        Cookie cookie = getDomainCookie();
        logger.info(":>>> domain cookie:{}", JSON.toJSONString(cookie));
        cookie.setValue(domain);
        //Https 安全cookie
        this.setCookieSecure(cookie);
        //不能被js访问的Cookie
//        cookie.setHttpOnly(true);
        cookie.saveTo(request, response);
        logger.info(":>>> Set domain cookie for session with {}", domain);
    }

    @Override
    protected String getUsername(ServletRequest request, ServletResponse response) {
        return super.getUsername(request, response);
    }

    private String getAccessCredentialsCookieValue(ServletRequest request, ServletResponse
            response) {
        if (!(request instanceof HttpServletRequest)) {
            logger.debug(":>>> Current request is not an HttpServletRequest - cannot get cookie.  " +
                    "Returning null.");
            return null;
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        //getSessionIdCookie()
        //WebUtils.
        return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
    }

    private WechatAuthBlackList get(String sysAppId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("sysAppId").is(sysAppId));
        return mongoTemplate.findOne(query, WechatAuthBlackList.class);
    }

    private void setCookieSecure(Cookie cookie) {
//        cookie.setHttpOnly(true);
        cookie.setPath("/");
        cookie.setSecure(true);
    }

    /**
     * Returns the host name or IP associated with the current subject.  This method is primarily
     * provided for use during construction of an <code>AuthenticationToken</code>.
     * <p/>
     * The default implementation merely returns {@link ServletRequest#getRemoteHost()}.
     *
     * @param request the incoming ServletRequest
     * @return the <code>InetAddress</code> to associate with the login attempt.
     */
    protected String getHost(ServletRequest request) {
        return request.getRemoteHost();
    }

    protected String getUsername(ServletRequest request) {
        return WebUtils.getCleanParam(request, getUsernameParam());
    }

    protected String getPassword(ServletRequest request) {
        return WebUtils.getCleanParam(request, getPasswordParam());
    }

    protected String getDomain(ServletRequest request) {
        return WebUtils.getCleanParam(request, getDomainParam());
    }

    /*protected boolean isRememberMe(ServletRequest request) {
        return WebUtils.isTrue(request, getRememberMeParam());
    }*/

    public String getUsernameParam() {
        return usernameParam;
    }

    public void setUsernameParam(String usernameParam) {
        this.usernameParam = usernameParam;
    }

    public String getPasswordParam() {
        return passwordParam;
    }

    public void setPasswordParam(String passwordParam) {
        this.passwordParam = passwordParam;
    }

    public String getDomainParam() {
        return domainParam;
    }

    public void setDomainParam(String domainParam) {
        this.domainParam = domainParam;
    }

    public String getUserIdParam() {
        return userIdParam;
    }

    public void setUserIdParam(String userIdParam) {
        this.userIdParam = userIdParam;
    }

    public String getComIdParam() {
        return comIdParam;
    }

    public void setComIdParam(String comIdParam) {
        this.comIdParam = comIdParam;
    }
}
